package com.csap.framework.oauth2.security;

import cn.dev33.satoken.LoginUtil;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.oauth2.logic.type.SecurityStrategy;
import cn.dev33.satoken.router.SaRouter;
import com.csap.framework.oauth2.FindUserService;
import com.csap.framework.oauth2.client.Oauth2ClientProperties;
import com.csap.framework.oauth2.client.user.cache.UserInfoCache;
import com.csap.framework.oauth2.security.filter.FilterProperties;
import com.csap.framework.oauth2.security.type.SecurityCondition;
import com.csap.framework.oauth2.security.user.UserContext;
import com.csap.framework.util.map.CsapConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * @Author ycf
 * @Date 2021/8/30 2:19 下午
 * @Version 1.0
 */
@Slf4j
public abstract class SecurityFilter<Children> extends RouterCache implements SecurityBeanFilter {
    protected Oauth2SecurityProperties oauth2SecurityProperties;
    protected Oauth2ClientProperties oauth2ClientProperties;
    /**
     * OAuth2 授权配置配置
     */
    protected SaOAuth2Config saOAuth2Config;
    /**
     * 认证函数：每次请求执行
     */
    protected SaFilterAuthStrategy auth = r -> {
    };

    /**
     * 异常处理函数：每次[认证函数]发生异常时执行此函数
     */
    protected SaFilterErrorStrategy error = e -> {
        throw new SaTokenException(e);
    };

    /**
     * 前置函数：在每次[认证函数]之前执行
     */
    protected SaFilterAuthStrategy beforeAuth = r -> {
    };

    /**
     * 用户权限策略
     */
    @Getter
    @Setter
    protected CsapConcurrentHashMap<SecurityStrategy, List<FilterProperties>> securityStrategy = new CsapConcurrentHashMap<>();
    /**
     * 拦截路由
     */
    protected CsapConcurrentHashMap<HttpMethod, Map<SecurityStrategy, Set<String>>> filter = new CsapConcurrentHashMap<>();

    public SecurityFilter(SaOAuth2Config saOAuth2Config, FindUserService findUserService, Oauth2SecurityProperties oauth2SecurityProperties, Oauth2ClientProperties oauth2ClientProperties) {
        this.saOAuth2Config = saOAuth2Config;
        this.oauth2SecurityProperties = oauth2SecurityProperties;
        this.oauth2ClientProperties = oauth2ClientProperties;
    }

    /**
     * 获取权限列表
     *
     * @param method           当前方法
     * @param securityStrategy 当前验证的权限策略
     * @return 权限列表
     */
    protected Set<String> getSecuritys(HttpMethod method, SecurityStrategy securityStrategy) {
        return filter.get(method).getOrDefault(securityStrategy, Collections.emptySet());
    }

    /**
     * 路径拦截处理
     *
     * @param methodName 方法名称
     */
    public Children filterMapping(String methodName) {
        HttpMethod method = HttpMethod.resolve(methodName);
        if (!filter.containsKey(method)) {
            return (Children) this;
        }
        String requestPath = SaHolder.getRequest().getRequestPath();
        //TODO 验证用户token以及权限
        SaRouter.match2(getSecuritys(method, SecurityStrategy.authenticated), getSecuritys(method, SecurityStrategy.permitAll), i -> {
            Boolean aBoolean = getOrNull(SecurityStrategy.permitAll, method, requestPath);
            if (Objects.nonNull(aBoolean)) {
                return aBoolean;
            }
            return !SaRouter.isMatchCurrURI(i);
        }, (exclude) -> computeIfAbsent(SecurityStrategy.permitAll, method, requestPath, exclude), () -> {
            beforeAuth.run(null);
            UserContext.set(UserInfoCache.getUserInfo(LoginUtil.getInstance().checkLogin()));
            deniedAuthorization(method, SecurityStrategy.role, SecurityStrategy.permission);
            auth.run(null);
        }, (i) -> {
            //已经执行了授权验证=true/false
            Object id;
            if (!i && Objects.nonNull(id = LoginUtil.getInstance().getLoginId(null))) {
                UserContext.set(UserInfoCache.getUserInfo(id));
                deniedAuthorization(method, SecurityStrategy.role, SecurityStrategy.permission);
            }
        });
        //TODO 验证应用clientToken以及作用域
        SaRouter.match(getSecuritys(method, SecurityStrategy.clientToken), getSecuritys(method, SecurityStrategy.clientPermitAll), () -> {
            SaOAuth2Util.checkClientToken();
            deniedAuthorization(method, SecurityStrategy.clientScope);
        });
        //TODO 验证应用accessToken以及作用域
        SaRouter.match(getSecuritys(method, SecurityStrategy.accessToken), () -> {
            SaOAuth2Util.checkAccessToken();
            deniedAuthorization(method, SecurityStrategy.accessScope);
        });
        return (Children) this;
    }

    /**
     * 验证权限
     *
     * @param method     当前请求的方法类型
     * @param strategies 权限策略
     */
    protected void deniedAuthorization(HttpMethod method, SecurityStrategy... strategies) {
        for (SecurityStrategy strategy : strategies) {
            getSecurityStrategy().getOrDefault(strategy, Collections.emptyList())
                    .forEach(i -> validate(i, method, strategy));
        }
    }

    /**
     * 验证当前账户的权限
     *
     * @param i                过滤属性
     * @param method           当前请求方法
     * @param securityStrategy 策略
     */
    protected void validate(FilterProperties i, HttpMethod method, SecurityStrategy securityStrategy) {
        if (!i.getMapping().containsKey(method)) {
            return;
        }
        SaRouter.match(i.getMapping().getOrDefault(method, Collections.emptySet()), i.getExclude().getOrDefault(method, Collections.emptySet()), () -> {
            if (i.getCondition().equals(SecurityCondition.AND)) {
                if (securityStrategy.equals(SecurityStrategy.role)) {
                    LoginUtil.getInstance().checkRoleAnd(i.getPermissions());
                } else if (securityStrategy.equals(SecurityStrategy.clientScope)) {
                    SaOAuth2Util.checkClientTokenScopeAnd(i.getPermissions());
                } else if (securityStrategy.equals(SecurityStrategy.accessScope)) {
                    SaOAuth2Util.checkAccessTokenScopeAnd(i.getPermissions());
                } else {
                    LoginUtil.getInstance().checkPermissionAnd(i.getPermissions());
                }
            } else {
                if (securityStrategy.equals(SecurityStrategy.role)) {
                    LoginUtil.getInstance().checkRoleOr(i.getPermissions());
                } else if (securityStrategy.equals(SecurityStrategy.clientScope)) {
                    SaOAuth2Util.checkClientTokenScopeOr(i.getPermissions());
                } else if (securityStrategy.equals(SecurityStrategy.accessScope)) {
                    SaOAuth2Util.checkAccessTokenScopeOr(i.getPermissions());
                } else {
                    LoginUtil.getInstance().checkPermissionOr(i.getPermissions());
                }
            }
        });
    }

    /**
     * 查询是否有响应的策略
     *
     * @param httpMethod       http方法
     * @param securityStrategy 权限策略
     * @return 是否存在
     */
    public Boolean containsSecurityStrategy(HttpMethod httpMethod, SecurityStrategy securityStrategy) {
        return filter.containsKey(httpMethod) && filter.get(httpMethod).containsKey(securityStrategy);
    }

    /**
     * 添加 [拦截路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addInclude(String... paths) {
        return addInclude(HttpMethod.values(), Arrays.asList(paths));
    }

    /**
     * 添加权限策略
     *
     * @param securityStrategy 权限策略
     * @param list             规则list
     * @return 自身
     */
    public Children addSecurityStrategy(SecurityStrategy securityStrategy, List<FilterProperties> list) {
        getSecurityStrategy().put(securityStrategy, list);
        return (Children) this;
    }

    /**
     * 添加 [拦截路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addInclude(HttpMethod[] httpMethod, Collection<String> paths) {
        for (HttpMethod method : httpMethod) {
            addAll(method, SecurityStrategy.authenticated, paths);
        }
        return (Children) this;
    }

    /**
     * 添加 [拦截路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addInclude(HttpMethod httpMethod, Collection<String> paths) {
        return addAll(httpMethod, SecurityStrategy.authenticated, paths);
    }

    /**
     * 添加拦截路径
     *
     * @param httpMethod 方法
     * @param filterType 策略类型
     * @param paths      路径
     * @return
     */
    public Children addAll(HttpMethod httpMethod, SecurityStrategy filterType, Collection<String> paths) {
        filter.computeIfAbsent(httpMethod, v -> new CsapConcurrentHashMap<>()).computeIfAbsent(filterType, v -> new HashSet<>()).addAll(paths);
        return (Children) this;
    }

    /**
     * 添加拦截路径
     *
     * @param httpMethod 方法
     * @param filterType 策略类型
     * @param paths      路径
     * @return
     */
    public Children addAllRefresh(HttpMethod httpMethod, SecurityStrategy filterType, Set<String> paths) {
        filter.computeIfAbsent(httpMethod, v -> new CsapConcurrentHashMap<>()).put(filterType, paths);
        return (Children) this;
    }

    /**
     * 添加 [放行路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addExclude(String... paths) {
        return addExclude(Arrays.asList(paths));
    }

    /**
     * 添加 [放行路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addExclude(HttpMethod httpMethod, Collection<String> paths) {
        return addAll(httpMethod, SecurityStrategy.permitAll, paths);
    }

    /**
     * 添加放行路径
     *
     * @param httpMethod 方法列表
     * @param paths      添加的路径
     * @return 自身
     */
    public Children addExclude(HttpMethod[] httpMethod, Collection<String> paths) {
        for (HttpMethod method : httpMethod) {
            addAll(method, SecurityStrategy.permitAll, paths);
        }
        return (Children) this;
    }

    /**
     * 添加 [放行路由]
     *
     * @param paths 路由
     * @return 对象自身
     */
    public Children addExclude(Collection<String> paths) {
        return addAll(HttpMethod.GET, SecurityStrategy.permitAll, paths);
    }

    /**
     * 写入[认证函数]: 每次请求执行
     *
     * @param auth see note
     * @return 对象自身
     */
    public Children setAuth(SaFilterAuthStrategy auth) {
        this.auth = auth;
        return (Children) this;
    }

    /**
     * 写入[异常处理函数]：每次[认证函数]发生异常时执行此函数
     *
     * @param error see note
     * @return 对象自身
     */
    public Children setError(SaFilterErrorStrategy error) {
        this.error = error;
        return (Children) this;
    }

    /**
     * 写入[前置函数]：在每次[认证函数]之前执行
     *
     * @param beforeAuth see note
     * @return 对象自身
     */
    public Children setBeforeAuth(SaFilterAuthStrategy beforeAuth) {
        this.beforeAuth = beforeAuth;
        return (Children) this;
    }
}
